<?php

namespace App\Services;

use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Connect as ConnectModel;
use App\Models\User as UserModel;
use App\Repos\Connect as ConnectRepo;
use App\Repos\User as UserRepo;
use App\Services\Auth\Api as AuthService;
use App\Services\Logic\Account\Register as RegisterService;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService;
use App\Services\User as UserService;
use App\Validators\Account as AccountValidator;
use GuzzleHttp\Client as HttpClient;

abstract class MiniProgram extends Service
{

    /**
     * @var string
     */
    protected $clientId;

    /**
     * @var string
     */
    protected $clientSecret;

    abstract public function getProvider(): int;

    abstract public function getOAuthConfig(): array;

    abstract public function session(string $code): string;

    abstract public function login(array $params): string;

    abstract protected function getCacheConfig(): array;

    abstract protected function parseOpenUser(array $params): array;


    public function __construct()
    {
        $config = $this->getOAuthConfig();

        $this->clientId = $config['client_id'];
        $this->clientSecret = $config['client_secret'];
    }

    public function bindLogin(array $params): string
    {
        [
            'account' => $account,
            'password' => $password,

        ] = $params;

        $validator = new AccountValidator();

        $user = $validator->checkUserLogin($account, $password);

        $openUser = $this->parseOpenUser($params);

        $this->handleConnectRelation($user, $openUser);

        $this->handleLoginNotice($user);

        $auth = $this->getAppAuth();

        $token = $auth->saveAuthInfo($user);

        return $token;
    }


    public function bindRegister(array $params): string
    {
        $openUser = $this->parseOpenUser($params);

        $registerService = new RegisterService();

        $account = $registerService->handle();

        $userRepo = new UserRepo();

        $user = $userRepo->findById($account->id);

        $this->handleConnectRelation($user, $openUser);

        $this->handleLoginNotice($user);

        $auth = $this->getAppAuth();

        $token = $auth->saveAuthInfo($user);

        return $token;
    }

    public function bindCurrent(array $params)
    {
        $openUser = $this->parseOpenUser($params);

        $user = $this->getLoginUser();

        $this->handleConnectRelation($user, $openUser);
    }

    public function getSessionKey(string $openId): ?string
    {
        return $this->getMpCache($openId, 'session_key');
    }

    public function getConnectModel(string $openId): ?ConnectModel
    {
        return $this->getConnectModelByProvicer($openId, $this->getProvider());
    }

    public function httpGet($uri, $params = [], $headers = []): string
    {
        $client = new HttpClient();

        $options = ['query' => $params, 'headers' => $headers];

        if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) {
            $uri = $this->baseUri.$uri;
        }

        $response = $client->get($uri, $options);

        return $response->getBody()->getContents();
    }

    public function httpPost($uri, $params = [], $headers = []): string
    {
        $client = new HttpClient();

        $options = ['query' => $params, 'headers' => $headers];

        if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) {
            $uri = $this->baseUri.$uri;
        }

        $response = $client->post($uri, $options);

        return $response->getBody()->getContents();
    }

    protected function getAppAuth(): AuthService
    {
        /**
         * @var $auth AuthService
         */
        $auth = $this->getDI()->get('auth');

        return $auth;
    }

    protected function getConnectModelByProvicer(string $openId, int $provider): ?ConnectModel
    {
        $connectRepo = new ConnectRepo();
        $connectModel = $connectRepo->findByOpenId($openId, $provider);

        return $connectModel ?: null;
    }

    protected function handleConnectRelation(UserModel $user, array $openUser)
    {
        $connectRepo = new ConnectRepo();

        $connect = $connectRepo->findByOpenId($openUser['id'], $openUser['provider']);

        if ($connect) {

            $connect->open_name = $openUser['name'];
            $connect->open_avatar = $openUser['avatar'];

            if ($connect->user_id != $user->id) {
                $connect->user_id = $user->id;
            }

            $connect->update();

        } else {

            $connect = new ConnectModel();

            $connect->user_id = $user->id;
            $connect->open_id = $openUser['id'];
            $connect->open_name = $openUser['name'];
            $connect->open_avatar = $openUser['avatar'];
            $connect->provider = $openUser['provider'];

            $connect->create();
        }
    }

    protected function handleLoginNotice(UserModel $user)
    {
        $service = new AccountLoginNoticeService();

        $service->createTask($user);
    }

    protected function getMpCache($openId, string $type)
    {
        [$pre] = $this->getCacheConfig();

        $provider = $this->getProvider();

        $cacheKey = "{$pre}.{$provider}.{$type}.{$openId}";

        return $this->getCache()->get($cacheKey);
    }

    protected function putMpCache($openId, string $type, $value)
    {
        [$pre, $life] = $this->getCacheConfig();

        $provider = $this->getProvider();

        $cacheKey = "{$pre}.{$provider}.{$type}.{$openId}";

        return $this->getCache()->save($cacheKey, $value, $life);
    }

    protected function loginByModel(ConnectModel $connectModel): string
    {
        $userService = new UserService();

        $user = $userService->get($connectModel->user_id);

        if (!$user) {
            throw new BadRequestException('account.not_found');
        }

        $this->handleLoginNotice($user);

        $auth = $this->getAppAuth();

        $token = $auth->saveAuthInfo($user);

        return $token;
    }
}
